{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Mining\n",
    "\n",
    "## Objective and Prerequisites\n",
    "\n",
    "Put your planning skills to the test in this example, where you’ll learn how to model and solve a production planning problem that involves optimizing the operations of a group of mines over a five-year period.\n",
    "\n",
    "More information on this type of model can be found in example # 7 of the fifth edition of Modeling Building in Mathematical Programming by H. P. Williams on pages 261 – 262 and 310 – 312.\n",
    "\n",
    "This modeling example is at the intermediate level, where we assume that you know Python and are familiar with the Gurobi Python API. In addition, you should have some knowledge about building mathematical optimization models.\n",
    "\n",
    "**Download the Repository** <br />\n",
    "You can download the repository containing this and other examples by clicking [here](https://github.com/Gurobi/modeling-examples/archive/master.zip). \n",
    "\n",
    "---\n",
    "## Problem Description\n",
    "\n",
    "A mining company needs to create a five-year operating plan for a certain area with four mines in it.\n",
    "\n",
    "They can only operate a maximum of three mines in this area in any given year. However, even though a mine may not operate in a given year, the company still must pay royalties on that mine if there is an expectation that it will operate again in the future. Otherwise, it can be permanently closed and no more royalties need to be paid.\n",
    "\n",
    "The yearly royalties due for each open mine (operating or not) are as follows:\n",
    "\n",
    "| <i></i> | Royalties |\n",
    "| --- | --- |\n",
    "| Mine 1 | $\\$5 Million$ |\n",
    "| Mine 2 | $\\$4 Million$ |\n",
    "| Mine 3 | $\\$4 Million$ |\n",
    "| Mine 4 | $\\$5 Million$ |\n",
    "\n",
    "There is a maximum amount of ore that can be extracted from each mine in a given year. These limits are as follows:\n",
    "\n",
    "| <i></i> | Max Production |\n",
    "| --- | --- |\n",
    "| Mine 1 | $2.0\\times10^6$ Tons |\n",
    "| Mine 2 | $2.5\\times10^6$ Tons |\n",
    "| Mine 3 | $1.3\\times10^6$ Tons |\n",
    "| Mine 4 | $3.0\\times10^6$ Tons |\n",
    "\n",
    "Each mine produces a different grade of ore. This grade is measured on a scale such that blending ores together results in a linear combination of the quality requirements. For example, if equal quantities of ore from two different mines were combined, the resulting ore would have a grade that is the average of the grade for each of the two ores. The grade for each mine’s ore is as follows:\n",
    "\n",
    "| <i></i> | Ore Quality |\n",
    "| --- | --- |\n",
    "| Mine 1 | 1.0 |\n",
    "| Mine 2 | 0.7 |\n",
    "| Mine 3 | 1.5 |\n",
    "| Mine 4 | 0.5 |\n",
    "\n",
    "Each year, the ore produced from each operating mine must be combined to produce ore of a certain grade. The yearly objectives for the combined ore are as follows:\n",
    "\n",
    "| <i></i> | Quality Target |\n",
    "| --- | --- |\n",
    "| Year 1 | 0.9 |\n",
    "| Year 2 | 0.8 |\n",
    "| Year 3 | 1.2 |\n",
    "| Year 4 | 0.6 |\n",
    "| Year 5 | 1.0 |\n",
    "\n",
    "The final blended ore sells for $\\$10$/ton. Revenues and costs for future years are discounted at the rate of $10\\%$ per annum.\n",
    "\n",
    "The key question for the mining company is: Which mines should be operated each year and how much ore should be extracted from each mine?\n",
    "\n",
    "This problem is based on a larger one faced by the firm of English China Clays, which had to in decide which mines to work. In that problem (in the 1970s), the goal was to work up to four mines out of 20 in each year.\n",
    "\n",
    "---\n",
    "## Model Formulation\n",
    "\n",
    "### Sets and Indices\n",
    "\n",
    "$t \\in \\text{Years}=\\{1,2,\\dots,5\\}$: Set of years.\n",
    "\n",
    "$m \\in \\text{Mines}=\\{1,2,\\dots,4\\}$: Set of mines.\n",
    "\n",
    "### Parameters\n",
    "\n",
    "$\\text{price} \\in \\mathbb{R}^+$: Selling price (in USD) of one ton of blended ore.\n",
    "\n",
    "$\\text{max_mines} \\in \\mathbb{N}$: Maximum number of mines that can operate in any given year.\n",
    "\n",
    "$\\text{royalties}_m \\in \\mathbb{R}^+$: Yearly royalties (in USD) for having mine $m$ open.\n",
    "\n",
    "$\\text{capacity}_m \\in \\mathbb{R}^+$: Maximum tons of ore that can be extracted from mine $m$ in any given year.\n",
    "\n",
    "$\\text{quality}_m \\in \\mathbb{R}^+$: Quality of the ore extracted from mine $m$.\n",
    "\n",
    "$\\text{target} \\in \\mathbb{R}^+$: Quality target of the blended ore in year $t$.\n",
    "\n",
    "$\\text{time_discount}_t \\in [0,1] \\subset \\mathbb{R}^+$: Time discount for revenue and cost in year $t$.\n",
    "\n",
    "### Decision Variables\n",
    "\n",
    "$\\text{blend}_t \\in \\mathbb{R}^+$: Tons of blended ore in year $t$.\n",
    "\n",
    "$\\text{extract}_{t,m} \\in \\mathbb{R}^+$: Tons of ore extracted from mine $m$ in year $t$.\n",
    "\n",
    "$\\text{working}_{t,m} \\in \\{0,1\\}$: 1 if mine $m$ is working in year $t$, 0 otherwise.\n",
    "\n",
    "$\\text{available}_{t,m} \\in \\{0,1\\}$: 1 if mine $m$ is open in year $t$, 0 otherwise.\n",
    "\n",
    "### Objective Function\n",
    "\n",
    "- **Profit**: Maximize the total profit (in USD) of the planning horizon.\n",
    "\n",
    "\\begin{equation}\n",
    "\\text{Maximize} \\quad Z = \\sum_{t \\in \\text{Years}}\\sum_{m \\in \\text{Mines}}{\\text{time_discount}_t*(\\text{price}*\\text{blend}_t-\\text{royalties}_m*\\text{extract}_{t,m})}\n",
    "\\tag{0}\n",
    "\\end{equation}\n",
    "\n",
    "### Constraints\n",
    "\n",
    "- **Operating Mines**: The total number of operating mines in year $t$ cannot exceed the limit.\n",
    "\n",
    "\\begin{equation}\n",
    "\\sum_{m \\in \\text{Mines}}{\\text{working}_{t,m}} \\leq \\text{max_mines} \\quad \\forall t \\in \\text{Years}\n",
    "\\tag{1}\n",
    "\\end{equation}\n",
    "\n",
    "- **Quality**: The final quality of the ore blended in year $t$ must meet the target.\n",
    "\n",
    "\\begin{equation}\n",
    "\\sum_{m \\in \\text{Mines}}{\\text{quality}_m*\\text{extract}_{t,m}} = \\text{target}_t*\\text{blended}_t \\quad \\forall t \\in \\text{Years}\n",
    "\\tag{2}\n",
    "\\end{equation}\n",
    "\n",
    "- **Mass Conservation**: Total tons of ore extracted in year $t$ should be equal to the Tons of the ore blended in that year.\n",
    "\n",
    "\\begin{equation}\n",
    "\\sum_{m \\in \\text{Mines}}{\\text{extract}_{t,m}} = \\text{blend}_t \\quad \\forall t \\in \\text{Years}\n",
    "\\tag{3}\n",
    "\\end{equation}\n",
    "\n",
    "- **Mine Capacity**: Total tons of ore extracted from mine $m$ in year $t$ cannot exceed the yearly capacity of that mine.\n",
    "\n",
    "\\begin{equation}\n",
    "\\sum_{m \\in \\text{Mines}}{\\text{extract}_{t,m}} \\leq \\text{capacity}_m*\\text{working}_{t,m} \\quad \\forall t \\in \\text{Years}\n",
    "\\tag{4}\n",
    "\\end{equation}\n",
    "\n",
    "- **Open to Operate**: Mine $m$ can be operated in year $t$ only if it is open in that year. \n",
    "\n",
    "\\begin{equation}\n",
    "\\text{working}_{t,m} \\leq \\text{available}_{t,m} \\quad \\forall (t,m) \\in \\text{Years} \\times \\text{Mines}\n",
    "\\tag{5}\n",
    "\\end{equation}\n",
    "\n",
    "- **Shut Down**: If mine $m$ is closed in year $t$, it cannot be opened again in the future.\n",
    "\n",
    "\\begin{equation}\n",
    "\\text{available}_{t+1,m} \\leq \\text{available}_{t,m} \\quad \\forall (t < 5,m) \\in \\text{Years} \\times \\text{Mines}\n",
    "\\tag{6}\n",
    "\\end{equation}\n",
    "\n",
    "---\n",
    "## Python Implementation\n",
    "\n",
    "We import the Gurobi Python Module and other Python libraries."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install gurobipy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "import gurobipy as gp\n",
    "from gurobipy import GRB\n",
    "\n",
    "# tested with Python 3.11 & Gurobi 11.0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Input Data\n",
    "We define all the input data of the model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Parameters\n",
    "\n",
    "years = [1, 2, 3, 4, 5]\n",
    "mines = [1, 2, 3, 4]\n",
    "\n",
    "royalties = {1: 5e6, 2: 4e6, 3: 4e6, 4: 5e6}\n",
    "capacity = {1: 2e6, 2: 2.5e6, 3: 1.3e6, 4: 3e6}\n",
    "quality  = {1: 1.0, 2: 0.7, 3: 1.5, 4: 0.5}\n",
    "target = {1: 0.9, 2: 0.8, 3: 1.2, 4: 0.6, 5: 1.0}\n",
    "time_discount = {year: (1/(1+1/10.0)) ** (year-1) for year in years}\n",
    "\n",
    "max_mines = 3\n",
    "price = 10"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Note:** The present value of a future amount at period $n$ is given by the formula: $\\text{present_value} = \\frac{1}{(1+\\text{interest_rate})^n}*\\text{future_value} $\n",
    "\n",
    "## Model Deployment\n",
    "We create a model and the variables. For each year and each mine, we have (i) a variable that captures production, in millions of tons, (ii) a decision variable which tells us if the mine is open, and (iii) a decision variable which tells us if the mine is operational."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Using license file c:\\gurobi\\gurobi.lic\n"
     ]
    }
   ],
   "source": [
    "mining = gp.Model('Mining')\n",
    "\n",
    "blend = mining.addVars(years, name=\"Blend\")\n",
    "extract = mining.addVars(years, mines, name=\"Extract\")\n",
    "working = mining.addVars(years, mines, vtype=GRB.BINARY, name=\"Working\")\n",
    "available = mining.addVars(years, mines, vtype=GRB.BINARY, name=\"Available\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, we insert the constraints.\n",
    "\n",
    "In each year only three mines can be operational."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "#1. Operating Mines\n",
    "\n",
    "OperatingMines = mining.addConstrs((working.sum(year, '*') <= max_mines for year in years), \"Operating_mines\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The quality of the ore from the mines multiplied by the amount that is mined must equal the needed blend quality multiplied by the quantity of blended ore.\n",
    "This ensures that the quality standards are satisfied."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "#2. Quality\n",
    "\n",
    "Quality = mining.addConstrs((gp.quicksum(quality[mine]*extract[year, mine] for mine in mines)\n",
    "                   == target[year]*blend[year] for year in years), \"Quality\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following constraint ensures that the tonnage of blended ore in each year equals the combined tonnage of the constituents."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "#3. Mass Conservation\n",
    "\n",
    "MassConservation = mining.addConstrs((extract.sum(year, '*') == blend[year] for year in years), \"Mass Conservation\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following constraint ensures that the mine can extract no more than the extract limit and also that there is only an output if the mine is operational."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "#4. Mine Capacity\n",
    "\n",
    "MineCapacity = mining.addConstrs((extract[year, mine] <= capacity[mine]*working[year, mine] for year, mine in extract), \"Capacity\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following constraint ensures that when the mine is operational, it also needs to be open."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Open to operate\n",
    "OpenToOperate = mining.addConstrs((working[year, mine] <= available[year, mine] for year, mine in available), \"Open to Operate\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following constraint forces a mine to be closed in all years subsequent to that in which it is first closed. If the mine is closed, it cannot be re-opened later:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Shutdown Mine\n",
    "ShutdownMine = mining.addConstrs((available[year+1, mine] <= available[year, mine]\n",
    "                   for year, mine in available if year < years[-1]), \"Shut down\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The total profit consists of the income from selling the blended ore minus the royalties payable. This is to be maximized. It can be written:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "#0. Objective function\n",
    "obj = gp.quicksum(price*time_discount[year]*blend[year] for year in years) \\\n",
    "- gp.quicksum(royalties[mine] * time_discount[year] * available[year, mine] for year, mine in available)\n",
    "mining.setObjective(obj, GRB.MAXIMIZE)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, the optimization process starts and Gurobi finds the optimal solution."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Gurobi Optimizer version 9.1.0 build v9.1.0rc0 (win64)\n",
      "Thread count: 4 physical cores, 8 logical processors, using up to 8 threads\n",
      "Optimize a model with 71 rows, 65 columns and 182 nonzeros\n",
      "Model fingerprint: 0xb3698537\n",
      "Variable types: 25 continuous, 40 integer (40 binary)\n",
      "Coefficient statistics:\n",
      "  Matrix range     [5e-01, 3e+06]\n",
      "  Objective range  [7e+00, 5e+06]\n",
      "  Bounds range     [1e+00, 1e+00]\n",
      "  RHS range        [3e+00, 3e+00]\n",
      "Found heuristic solution: objective -0.0000000\n",
      "Presolve removed 13 rows and 13 columns\n",
      "Presolve time: 0.00s\n",
      "Presolved: 58 rows, 52 columns, 135 nonzeros\n",
      "Variable types: 16 continuous, 36 integer (36 binary)\n",
      "\n",
      "Root relaxation: objective 1.577309e+08, 40 iterations, 0.00 seconds\n",
      "\n",
      "    Nodes    |    Current Node    |     Objective Bounds      |     Work\n",
      " Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time\n",
      "\n",
      "     0     0 1.5773e+08    0    4   -0.00000 1.5773e+08      -     -    0s\n",
      "H    0     0                    1.189074e+08 1.5773e+08  32.7%     -    0s\n",
      "H    0     0                    1.302711e+08 1.5773e+08  21.1%     -    0s\n",
      "H    0     0                    1.393620e+08 1.5524e+08  11.4%     -    0s\n",
      "     0     0 1.5479e+08    0    4 1.3936e+08 1.5479e+08  11.1%     -    0s\n",
      "H    0     0                    1.468620e+08 1.5479e+08  5.40%     -    0s\n",
      "\n",
      "Cutting planes:\n",
      "  Implied bound: 4\n",
      "  MIR: 3\n",
      "  Flow cover: 1\n",
      "  RLT: 3\n",
      "  Relax-and-lift: 3\n",
      "\n",
      "Explored 1 nodes (48 simplex iterations) in 0.06 seconds\n",
      "Thread count was 8 (of 8 available processors)\n",
      "\n",
      "Solution count 5: 1.46862e+08 1.39362e+08 1.30271e+08 ... -0\n",
      "\n",
      "Optimal solution found (tolerance 1.00e-04)\n",
      "Best objective 1.468619743642e+08, best bound 1.468619743642e+08, gap 0.0000%\n"
     ]
    }
   ],
   "source": [
    "mining.optimize()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## Analysis\n",
    "\n",
    "The optimal solution results in a profit of $\\$146.8620$ million with the following production plan for each mine in each year (quantities expressed in millions of tons):\n",
    "\n",
    "### Extraction Plan\n",
    "This plan determines the millions of tons of ore extracted from each mine (columns)  in each year (rows) of the planning horizon. For example, 1.3 millions of tons of ore will be extracted from mine 3 during the year 2."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>1</th>\n",
       "      <th>2</th>\n",
       "      <th>3</th>\n",
       "      <th>4</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>2.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>1.3</td>\n",
       "      <td>2.45</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>0.00</td>\n",
       "      <td>2.50</td>\n",
       "      <td>1.3</td>\n",
       "      <td>2.20</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>1.95</td>\n",
       "      <td>0.00</td>\n",
       "      <td>1.3</td>\n",
       "      <td>0.00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>0.12</td>\n",
       "      <td>2.50</td>\n",
       "      <td>0.0</td>\n",
       "      <td>3.00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>2.00</td>\n",
       "      <td>2.17</td>\n",
       "      <td>1.3</td>\n",
       "      <td>0.00</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "      1     2    3     4\n",
       "1  2.00  0.00  1.3  2.45\n",
       "2  0.00  2.50  1.3  2.20\n",
       "3  1.95  0.00  1.3  0.00\n",
       "4  0.12  2.50  0.0  3.00\n",
       "5  2.00  2.17  1.3  0.00"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rows = years.copy()\n",
    "columns = mines.copy()\n",
    "extraction = pd.DataFrame(columns=columns, index=rows, data=0.0)\n",
    "\n",
    "for year, mine in extract.keys():\n",
    "    if (abs(extract[year, mine].x) > 1e-6):\n",
    "        extraction.loc[year, mine] = np.round(extract[year, mine].x / 1e6, 2)\n",
    "extraction"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Sales Plan\n",
    "This plan defines the millions of tons of blended ore to sell during each year of the planning horizon. For example, we plan to sell 5.62 million tons of blended ore during year 4."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Sales</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>5.75</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>6.00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>3.25</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>5.62</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>5.47</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   Sales\n",
       "1   5.75\n",
       "2   6.00\n",
       "3   3.25\n",
       "4   5.62\n",
       "5   5.47"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rows = years.copy()\n",
    "sales = pd.DataFrame(columns=['Sales'], index=rows, data=0.0)\n",
    "\n",
    "for year in blend.keys():\n",
    "    if (abs(blend[year].x) > 1e-6):\n",
    "        sales.loc[year, 'Sales'] = np.round(blend[year].x / 1e6, 2)\n",
    "sales"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Note:** If you want to write your solution to a file, rather than print it to the terminal, you can use the model.write() command. An example implementation is:\n",
    "\n",
    "`mining.write(\"mining-output.sol\")`\n",
    "\n",
    "---\n",
    "## References\n",
    "\n",
    "H. Paul Williams, Model Building in Mathematical Programming, fifth edition.\n",
    "\n",
    "Copyright © 2020 Gurobi Optimization, LLC"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
